﻿namespace PowerSong.BibleSupport.FileArchive
    {
    using System;
    using System.IO;

    /// <summary>
    /// Represents a file in an archive.
    /// </summary>
    internal class ArchiveFile
        {

        private string fileName = string.Empty;

        private BinaryReader file = null;

        private FileTable fileTable = null;

        /// <summary>
        /// Do not allow construction of the <see cref="ArchiveFile" /> class.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        private ArchiveFile(string fileName)
            {
            this.fileName = fileName;
            this.file = new BinaryReader(new FileStream(fileName, FileMode.Open, FileAccess.Read, FileShare.Read));
            this.fileTable = GetFileTable();
            }

        /// <summary>
        /// Closes the open archive file.
        /// </summary>
        internal void Close()
            {
            file.Close();
            }

        /// <summary>
        /// Creates a new archive file based on the specified file name and file table.
        /// </summary>
        /// <param name="fileName">Name of the file.</param>
        /// <param name="fileTable">The file table that specifies what goes into the archive.</param>
        internal static void Create(string fileName, FileTable fileTable)
            {

            // Create the archive file
            using (var file = new BinaryWriter(new FileStream(fileName, FileMode.Create)))
                {

                // Write file table
                WriteFileTable(file, fileTable);

                // Write the files
                for (var fileIndex = 0; fileIndex <= fileTable.FileCount - 1; fileIndex++)
                    {
                    var tableEntry = fileTable.GetFile(fileIndex);
                    fileTable.SetFilePosition(tableEntry.VirtualFileName, (int)file.BaseStream.Position);
                    file.Write(tableEntry.Content.Length);
                    file.Write(tableEntry.Content);
                    }

                // Write the amended file table
                WriteFileTable(file, fileTable);

                }

            }

        /// <summary>
        /// Loads the specified file.
        /// </summary>
        /// <param name="fileName">Name of the file to load.</param>
        /// <returns>The archive file at the given location.</returns>
        internal static ArchiveFile Load(string fileName)
            {
            return new ArchiveFile(fileName);
            }

        internal byte[] GetFile(string virtualFileName)
            {
            file.BaseStream.Position = fileTable.GetFilePosition(virtualFileName);
            var fileLength = file.ReadInt32();
            return file.ReadBytes(fileLength);
            }

        /// <summary>
        /// Gets all files in the archive.
        /// </summary>
        /// <returns>A list of all of the files in the archive.</returns>
        internal string[] GetAllFiles()
            {
            return fileTable.VirtualFileNames;
            }

        private FileTable GetFileTable()
            {

            file.BaseStream.Position = 0;
            var fileCount = file.ReadInt32();
            var result = new FileTable();
            for (int fileIndex = 0; fileIndex <= fileCount - 1; fileIndex++)
                {

                // Get data about the file
                var virtualFileName = file.ReadString();
                var position = file.ReadInt32();

                // Virtual file names with extensions were supported in PowerSong 1, but they are not in this version
                // (This allows us to use PowerSong 1 compatible bible translations without having to create new archives)
                virtualFileName = Path.GetFileNameWithoutExtension(virtualFileName);

                // Do some sanity checking
                if (virtualFileName.Trim().Length == 0) throw new ApplicationException("Virtual file name has not been specified.");
                if (position == 0) throw new ApplicationException("Invalid position for file '" + virtualFileName + "' in archive.");

                // Add the file to the list
                result.AddFile(virtualFileName, null, position);

                }
            return result;

            }

        private static void WriteFileTable(BinaryWriter file, FileTable fileTable)
            {
            file.BaseStream.Position = 0;
            file.Write(fileTable.FileCount);
            for (var fileIndex = 0; fileIndex <= fileTable.FileCount - 1; fileIndex++)
                {
                var tableEntry = fileTable.GetFile(fileIndex);
                file.Write(tableEntry.VirtualFileName);
                file.Write(tableEntry.Position);
                }

            }

        }

    }
